#include "ESPAsyncWebServer.h"
#include "WebHandlerImpl.h"
#include "WebResponseImpl.h"

#define TAG "WebHandlers"

/// @brief 构造一个处理器
/// @param uri 处理器负责处理的URI前缀
/// @param path 目标文件系统的真实路径
/// @param cache_control 缓存控制头
AsyncStaticWebHandler::AsyncStaticWebHandler(const char* uri, const char* path, const char* cache_control)
    : m_uri(uri)
    , m_path(path)
    , m_default_file("index.htm")
    , m_cache_control(cache_control)
    , m_last_modified("")
    , m_callback(nullptr)
{
    // 确保处理器绑定的URI是以'/'开头
    size_t uri_len = m_uri.length();
    if (uri_len == 0 || m_uri[0] != '/') {
        m_uri = "/" + m_uri;
    }
    // 确保映射的文件系统root路径以'/'开头
    size_t path_len = m_path.length();
    if (path_len == 0 || m_path[0] != '/') {
        m_path = "/" + m_path;
    }

    // 如果路径以'/'结尾，则表示path是目录
    m_isDir = (m_path[path_len - 1] == '/');

    // 移除路径后的'/'
    if (m_uri[uri_len - 1] == '/') {
        m_uri = m_uri.substr(0, uri_len - 1);
    }
    if (m_path[path_len - 1] == '/') {
        m_path = m_path.substr(0, path_len - 1);
    }

    m_gzipFirst = false;
    m_gzipStats = 0xF8;
}

/// @brief 设置资源 Last-Modified 的数值（上次修改时间）
/// @param last_modified
/// @return
AsyncStaticWebHandler &AsyncStaticWebHandler::setLastModified(struct tm* last_modified)
{
    char result[30];
    strftime(result, 30, "%a, %d %b %Y %H:%M:%S %Z", last_modified);
    return setLastModified((const char*)result);
}

/// @brief 检查给定的路径是否是在文件系统中存在的文件
/// @param path 文件路径
/// @return
bool AsyncStaticWebHandler::FILE_IS_REAL(const char* path) const
{
    struct stat path_stat;
    if (stat(path, &path_stat) == -1) {
        return false;
    }
    if (S_ISDIR(path_stat.st_mode)) {
        return false;
    }
    return true;
}

/// @brief 检查指定路径的文件是否存在，并根据请求次数设置优先发送gzip文件标志
/// @param path 文件路径
/// @return 存在时返回true并将文件路径存放在请求的_tempObject成员变量中
bool AsyncStaticWebHandler::m_fileExists(AsyncWebServerRequest* req, const std::string &path)
{
    bool fileFound = false;
    bool gzipFound = false;

    std::string gzip = path + ".gz";
    if (m_gzipFirst) {
        gzipFound = FILE_IS_REAL(gzip.c_str());
        if (!gzipFound) {
            fileFound = FILE_IS_REAL(path.c_str());
        }
    } else {
        fileFound = FILE_IS_REAL(path.c_str());
        if (!fileFound) {
            gzipFound = FILE_IS_REAL(gzip.c_str());
        }
    }

    bool found = fileFound || gzipFound;

    if (found) {
        // 提取文件路径并保存到_tempObject中
        size_t pathLen = path.length();
        char* _tempPath = (char*) malloc(pathLen + 1);
        snprintf(_tempPath, pathLen + 1, "%s", path.c_str());
        req->_tempObject = (void*)_tempPath;

        // 根据gzip命中次数更新gzip状态记录器（m_gzipStats）
        m_gzipStats = (m_gzipStats << 1) + (gzipFound ? 1 : 0);
        if (m_gzipStats == 0x00) {
            m_gzipFirst = false;
        } else if (m_gzipStats == 0xff) {
            m_gzipFirst = true;
        } else {
            m_gzipFirst = m_countBits(m_gzipStats) > 4;
        }
    }

    return found;
}

/// @brief 计算给定数据的二进制中1所占的个数
/// @param value
/// @return
uint8_t AsyncStaticWebHandler::m_countBits(const uint8_t value) const
{
    uint8_t w = value;
    uint8_t i;
    for (i = 0; w != 0; i++) {
        w &= w - 1;
    }

    return i;
}

/// @brief 根据请求获取请求文件的真实路径
/// @return 失败则返回false，成功则将文件路径保存于req->_tempObject
bool AsyncStaticWebHandler::m_getFile(AsyncWebServerRequest* req)
{
    std::string path = req->url().substr(m_uri.length());

    // 当请求的是根路径、或是以/为结束符时，跳过文件存在检查（使用默认文件）
    size_t path_len = path.length();
    bool canSkipFileCheck = (m_isDir && path_len == 0) || (path_len && path[path_len - 1] == '/');

    path = m_path + path;

    // 请求的不是目录时，检查文件是否存在
    if (!canSkipFileCheck && m_fileExists(req, path)) {
        return true;
    }

    // 请求的是目录，但没有设置默认文件
    if (m_default_file.length() == 0) {
        return false;
    }

    if (path_len == 0 || path[path_len - 1] != '/') {
        path += '/';
    }
    path += m_default_file;

    return m_fileExists(req, path);
}

/// @brief 检查请求能否被本处理器处理，在req中标记感兴趣的头部信息
/// @return 可以处理返回true并在req->_tempObject存放请求文件真实路径
bool AsyncStaticWebHandler::canHandle(AsyncWebServerRequest* req)
{
    if (req->method() != HTTP_GET
            || !req->url().starts_with(m_uri)
            || !req->isExpectedRequestedConnType(RCT_DEFAULT, RCT_HTTP)) {
        return false;
    }

    if (m_getFile(req)) {
        //根据处理器标记感兴趣的头
        if (m_last_modified.length()) {
            req->addInterestingHeader("If-Modified-Since");
        }
        if (m_cache_control.length()) {
            req->addInterestingHeader("If-None-Match");
        }

        return true;
    }
    return false;
}

/// @brief .................可能存在问题
void AsyncStaticWebHandler::handleRequest(AsyncWebServerRequest* req)
{
    std::string filename = std::string((char*)req->_tempObject);
    auto ptr = req->_tempObject;
    req->_tempObject = nullptr;
    free(ptr);

    ESP_LOGI(TAG, "静态处理器[AsyncStaticWebHandler]正在运行");

    if ((!m_username.empty() && !m_password.empty()) && !req->authenticate(m_username.c_str(), m_password.c_str())) {
        return req->requestAuthentication();    // 未通过认证则发送401
    }

    const char* file = filename.c_str();
    struct stat fileStat;

    if (stat(file, &fileStat) != -1) {
        std::string etag = std::to_string(fileStat.st_size);
        if (!m_last_modified.empty() && m_last_modified == req->header("If-Modified-Since")) {
            req->send(304);
        } else if (!m_cache_control.empty() && req->hasHeader("If-None-Match") && req->header("If-None-Match") == etag) {
            AsyncWebServerResponse* response = new AsyncBasicResponse(304);
            response->addHeader("Cache-Control", m_cache_control);
            response->addHeader("ETag", etag);
            req->send(response);
        } else {
            AsyncWebServerResponse* response = new AsyncFileResponse(filename, std::string(), false, m_callback);
            if (!m_last_modified.empty()) {
                response->addHeader("Last-Modified", m_last_modified);
            }
            if (!m_cache_control.empty()) {
                response->addHeader("Cache-Control", m_cache_control);
                response->addHeader("ETag", etag);
            }
            req->send(response);
        }
    } else {
        req->send(404);
    }
}
